Nuestro objetivo es crear un modelo lineal múltiple para explicar el precio de venta en dólares de las propiedades en Capital Federal reportadas por la empresa Properati Argentina.
Vamos a utilizar datos del 2019 para no incorporar comportamientos atípicos ocasionados por la pandemia del COVID-19.
Nuestra idea subyacente de cómo se puede explicar el precio es:
\(precio = \beta_0 +\beta_1X_1+\beta_2X_2+...+\epsilon\)
library(tidyverse)
library(tidymodels)
library(rsample)
library(ggplot2)
library(GGally)
Vamos a trabajar con un subconjunto de datos que surgió de un trabajo de limpieza y tratamiento de outliers que se puede consultar en el notebook de Preprocesamiento Dataset Properati, correspondiente a precios de propiedades en Capital Federal, cuyo precio está expresado en dolares (USD), el tipo de propiedad corresponde a Departamento, PH o Casa, y el tipo de operacion sea Venta.
datos_properati <- read.csv("properati_preprocesado.csv")
glimpse(datos_properati)
Rows: 49,835
Columns: 9
input string 2 is invalid in this localeinput string 17 is invalid in this localeinput string 20 is invalid in this localeinput string 25 is invalid in this localeinput string 40 is invalid in this locale
$ id [3m[38;5;246m<fct>[39m[23m AfdcsqUSelai1ofCAq2B0Q==, ESzybdH7YU2uIU1/kHtRGw==, r22OfzZ3kXooSPoE5HMuZQ==, atZQXVtyfG7+OiX6gYY3lA==, R…
$ l3 [3m[38;5;246m<fct>[39m[23m Velez Sarsfield, Nu<f1>ez, Almagro, Almagro, Almagro, Almagro, Almagro, Almagro, Almagro, Almagro, Almagr…
$ rooms [3m[38;5;246m<int>[39m[23m 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 4, 1, 2, 1, 1, 2, 2, 2, 2, 2, 2, …
$ bathrooms [3m[38;5;246m<int>[39m[23m 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, …
$ surface_total [3m[38;5;246m<int>[39m[23m 95, 44, 40, 49, 40, 40, 40, 49, 40, 23, 40, 40, 32, 40, 36, 90, 45, 54, 40, 33, 40, 40, 36, 38, 174, 32, …
$ surface_covered [3m[38;5;246m<int>[39m[23m 69, 38, 37, 44, 37, 37, 37, 44, 37, 23, 37, 37, 30, 34, 33, 90, 35, 48, 40, 32, 34, 36, 33, 34, 174, 29, …
$ price [3m[38;5;246m<dbl>[39m[23m 199900, 147000, 92294, 115000, 77000, 88900, 88798, 110975, 92943, 69000, 99000, 96984, 125000, 99500, 12…
$ property_type [3m[38;5;246m<fct>[39m[23m Casa, Departamento, Departamento, Departamento, Departamento, Departamento, Departamento, Departamento, D…
$ precio_en_miles [3m[38;5;246m<int>[39m[23m 200, 147, 92, 115, 77, 89, 89, 111, 93, 69, 99, 97, 125, 100, 122, 285, 150, 140, 110, 110, 96, 153, 108,…
La limpieza consistió en: eliminar la variable bedrooms por registrar gran presencia de faltantes (40%) y correlación positiva fuerte con la variable rooms (0.97), eliminar los registros con faltantes, descartar registros donde la superficie total resulta inferior a la cubierta, por tratarse probablemente de algún error de carga. Por último, se realizó un tratamiento de los outliers, empleando las técnicas vistas en la clase 3, quedando como resultado un dataset de 49.835 observaciones y 9 columnas, con el que trabajaremos a continuación.
En este caso ya están preseleccionadas 9 variables:
datos_properati %>%
head()
Veamos cómo es la correlación entre las variables numéricas.
# graficamos con ggpairs coloreando por property type
g <- ggpairs(datos_properati %>% select(-c(id,l3)), aes(color = property_type),
upper = list(continuous = wrap("cor", size = 3, hjust=0.5)), legend = 25) +
theme(axis.text.x = element_text(angle = 90, hjust = 1), legend.position = "bottom") +
theme_bw()
# hacemos un loop para cambiar los colores del gráfico
for(i in 1:g$nrow) {
for(j in 1:g$ncol){
g[i,j] <- g[i,j] +
scale_fill_brewer(palette="Dark2") +
scale_color_brewer(palette="Dark2")
}
}
g
Respecto de nuestra variable a predecir, el precio, observamos que:
Existe una correlación positiva fuerte con las superficies total y cubierta. Respecto a estas últimas existe una gran diferencia en la correlación según el tipo de propiedad. Las casas parecen presentar menor correlación que los departamentos y PHs.
Existe una correlación positiva menor con el número de habitaciones y baños.
En este caso para evaluar los modelos vamos a realizar una partición entre dataset de entrenamiento (75%) y testeo (25%) usando la función initial_split del paquete rsample de tidymodels.
# fijamos semilla
set.seed(2021)
# Partición Train y Test, indicando proporción
train_test <- initial_split(datos_properati, prop = 0.75)
train_data <- training(train_test)
test_data <- testing(train_test)
# vemos las dimensiones de cada particion
train_data %>%
dim_desc()
[1] "[37,376 x 9]"
test_data %>%
dim_desc()
[1] "[12,459 x 9]"
El dataset de test lo utilizaremos en la siguiente clase.
El modelo de regresión lineal múltiple es un modelo para la variable aleatoria Y cuando se conocen las variables regresoras. Es múltiple ya que vincula una serie de variables predictoras con Y.
El modelo en términos de las variables:
\[Y_i = β_0 + β_1X_{i1} + β_2X_{i2} + · · · + β_{p-1}X_{ip-1} + ε_i\] donde \(β_0\), \(β_1\),.., \(β_{p−1}\) son parámetros desconocidos, \(X_{i1}\), \(X_{i2}\), …, \(X_{ip-1}\) son los valores de las variables predictoras medidas en el i-ésimo individuo, \(Y_i\) es la variable respuesta medida en el i-ésimo individuo (observado) y \(ε_i\) es el error para el individuo i-ésimo (no observable).
Supuestos del modelo lineal
Se pueden resumir como \(ϵ_i\) ~ \(N(0,σ^2)\) para todo \(1<i<n\), independientes entre sí.
El modelo en términos de la esperanza condicional de Y dadas \(X_1\), \(X_2\),…, \(X_{p-1}\):
\[E(Y|X_1,X_2,...X_{p-1}) = β_0 + β_1X_{i1} + β_2X_{i2} + · · · + β_{p-1}X_{ip-1}\]
El modelo se denomina lineal puesto que la esperanza de Y condicional a las X’s depende linealmente de las covariables \(X_1\), \(X_2\),…, \(X_{p-1}\).
Se quiere ajustar un modelo para el precio de las propiedades en función de 2 variables:
\(precio = \beta_0 + \beta_1x_{i1} + \beta_2x_{i2} + \epsilon_i\)
Veamos cómo se interpretan los ajustes para los distintos tipos de predictores.
Armemos un modelo para predecir el precio en función de la superficie cubierta y la cantidad de habitaciones. Veamos los resultados del modelo empleando la función tidy().
# ajustamos modelo lineal multiple
modelo_sc_r <- lm(price ~ surface_covered + rooms, data = train_data)
# Resumen del modelo
tidy_sc_r <- tidy(modelo_sc_r, conf.int = TRUE)
tidy_sc_r
El valor de \(\hat{\beta_0}\) (ordenada al origen) es 21639 dólares, lo que corresponde al precio esperado de una propiedad con 0 habitaciones y sin superficie. Lo cual, este caso carecería de sentido ya que las propiedades deberían tener superficie y al menos alguna habitación/ambiente.
El coeficiente estimado de \(\hat{\beta_{surface\_covered}}\) (superficie cubierta) es de 3183 dólares, lo que indica que si mantenemos el número de habitaciones constante, cada incremento de un m2 adicional de superficie corresponde a un aumento de 3183 dólares, en promedio en el precio de la propiedad. O lo que es igual, dadas dos propiedades con la misma cantidad de habitaciones pero teniendo una un m2 más de superficie que la otra, el precio esperado para la de mayor superficie será 3183 dólares más alto que la de menor superficie.
¿Cómo se interpretaría el coeficiente estimado del número de habitaciones?
Armemos un modelo para predecir el precio de la propiedad en función de la superficie cubierta y el tipo de propiedad (property_type), que es categórica con tres niveles (Casa, Departamento o PH). Para ello, vamos a analizar primero el comportamiento de la variable que queremos predecir para cada tipo de propiedad a través de un boxplot.
# armamos boxplots paralelos de precio segun el tipo de propiedad (casa, dpto o ph)
ggplot(data = train_data, aes(y = precio_en_miles, group = property_type, fill = property_type)) +
geom_boxplot() +
scale_fill_brewer(palette="Dark2") +
theme_bw() +
theme(axis.text.x=element_blank(),
axis.ticks.x=element_blank()) +
labs(title = "Boxplots de precio según tipo de propiedad", subtitle = "En miles de dólares") +
labs(y = "Precio en miles de USD") +
labs(x = "Tipo de propiedad") +
facet_wrap(~property_type)
# Acotando la escala del grafico para visualizar mejor el rango intercuartil
ggplot(data = train_data, aes(y = precio_en_miles, group = property_type, fill = property_type)) +
geom_boxplot() +
scale_fill_brewer(palette="Dark2") +
theme_bw() +
scale_y_continuous(limits = c(0, 1000)) +
theme(axis.text.x=element_blank(),
axis.ticks.x=element_blank()) +
labs(title = "Boxplots de precio según tipo de propiedad", subtitle = "En miles de dólares") +
labs(y = "Precio en miles de USD") +
labs(x = "Tipo de propiedad") +
facet_wrap(~property_type)
Analizando la variable de tipo de propiedad en función de la variable a predecir, se observa una diferencia en los precios según el tipo de propiedad. En particular, las casas presentan mayores precios y a su vez, mayor variabilidad que los departamentos y PHs.
Veamos qué ocurre cuando ajustamos el modelo:
# ajustamos el modelo de superficie y tipo de propiedad
modelo_sc_pt <- lm(price ~ surface_covered + property_type, data = train_data)
tidy_sc_pt <- tidy(modelo_sc_pt, conf.int = TRUE)
tidy_sc_pt
¿Cómo cambia la interpretación de los coeficientes para la variable categórica?
El modelo de regresión lineal en este caso consiste simplemente en expresar la media del nivel de precios en cada población (de casas, departamentos o PHs) mediante tres coeficientes distintos, donde:
\(\beta_0\) (categoría basal de variable categórica) es la media del precio para las Casas sin superficie. En este modelo es de -2.1975210^{5} dólares, lo que carece de sentido económico ya que las casas deberían tener alguna superficie y no podrían tener un precio negativo.
\(\beta_0 + \beta_{property\_typeDepartamento}\) es la media del precio para los departamentos, dada la superficie. Por lo tanto, \(\beta_{property\_typeDepartamento}\) es la diferencia en los niveles medios de precios de los departamentos respecto de las casas (categoría basal).Es decir, \(\beta_{property\_typeDepartamento}\) (2.1871410^{5}) indica cuánto más alta es la función de respuesta (precio) para los departamentos respecto de las casas (categoría basal), dada la superficie.
Vemos que el nivel medio del precio es una función lineal de la superficie de la propiedad, con una misma pendiente \(\beta_{surface_total}\) 3310 para casas, departamentos y PHs.
¿Cómo se interpretaría entonces \(\beta_{property\_typePH}\)? ¿Y \(\beta_0 + \beta_{property\_typePH}\)?
A continuación se muestra el gráfico de esta situación en que tenemos una variable categórica con tres niveles y una numérica. De la interpretación de coeficientes, se pudo ver que la regresión se puede expresar como rectas paralelas con igual pendiente pero distinto intercepto. Veamos cómo hacerla.
# Accedemos a la información de los coeficientes estimados
intercepto_C = modelo_sc_pt$coefficients[1] # β0
pendiente = modelo_sc_pt$coefficients[2] # β1
intercepto_D = modelo_sc_pt$coefficients[1] + modelo_sc_pt$coefficients[3] # β0 + β2
intercepto_PH = modelo_sc_pt$coefficients[1] + modelo_sc_pt$coefficients[4] # β0 + β3
# Graficamos el dataset y el modelo
train_data %>% ggplot(., aes(x = surface_covered, y = price)) +
geom_point() + #capa de los datos
geom_abline(intercept = intercepto_C, slope = pendiente, color = "forestgreen", size=1) + # capa del modelo
geom_abline(intercept = intercepto_D, slope = pendiente, color = "darkorange", size=1) + # capa del modelo
geom_abline(intercept = intercepto_PH, slope = pendiente, color = "slateblue3", size=1) + # capa del modelo
theme_bw() +
labs(title="Modelo Lineal Múltiple: Superficie y Tipo de Propiedad", x="Superficie en m2", y="Precio en miles de USD")
La variable barrios (l3) tiene 57 niveles distintos. Veamos a través de boxplots paralelos cómo se comportan.
length(unique(datos_properati$l3)) # 57 barrios
[1] 57
# armo scatterplot de precios en miles en función de superficie total
ggplot( datos_properati, aes(x = fct_reorder(l3, price, .desc = T), y = price/1000)) +
geom_boxplot(alpha = 0.75, aes(fill = l3)) +
theme_minimal() +
theme(legend.position = 'none')+
labs(y = "Precios en miles", x = "Barrios") +
ggtitle("Boxplots de precios en función de los barrios")+
theme (axis.text.x = element_text(face="italic", colour="dark grey", size = 8, angle = 90))
(process:6086): Pango-WARNING **: 17:57:46.610: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.617: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.617: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.617: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.617: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.617: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.618: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.618: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.618: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.618: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.618: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.619: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.619: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.619: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.619: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.619: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.620: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.620: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.730: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.730: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.730: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.730: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.731: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.731: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.731: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.731: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.731: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.731: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.732: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.732: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.732: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.732: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.733: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.733: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.733: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.733: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.734: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.734: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.734: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.734: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.735: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.735: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.735: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.735: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.736: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.736: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.736: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.736: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.737: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.737: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.737: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.737: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.738: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.738: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.738: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.738: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.739: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.739: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.739: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.739: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.740: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.740: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.740: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.740: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.741: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.741: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.743: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.744: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.744: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.744: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.744: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.744: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.745: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.745: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.745: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.745: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.745: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.746: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.746: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.746: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.747: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.747: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.747: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.747: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.747: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.748: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.749: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.749: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.749: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:46.749: Invalid UTF-8 string passed to pango_layout_set_text()
# armamos scatterplot de precios en miles en función de superficie total
ggplot(datos_properati, aes(x = fct_reorder(l3, price, .desc = T), y = price/1000)) +
geom_boxplot(outlier.shape = NA, alpha = 0.75, aes(fill = l3)) +
theme_minimal() +
theme(legend.position = 'none')+
labs(y = "Precios en miles", x = "Barrios") +
scale_y_continuous(limits = c(0, 1200)) +
ggtitle("Boxplots de precios en función de los barrios")+
theme (axis.text.x = element_text(face="italic", colour="dark grey", size = 8, angle = 90))
(process:6086): Pango-WARNING **: 17:57:47.385: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.385: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.386: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.386: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.386: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.386: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.386: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.386: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.386: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.387: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.387: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.387: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.388: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.388: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.388: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.388: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.389: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.389: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.469: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.469: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.469: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.470: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.470: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.470: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.470: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.470: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.471: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.471: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.471: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.471: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.472: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.472: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.472: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.472: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.473: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.473: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.475: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.475: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.475: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.475: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.476: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.476: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.476: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.476: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.476: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.476: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.477: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.477: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.478: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.478: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.478: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.478: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.479: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.479: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.479: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.479: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.479: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.479: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.480: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.480: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.481: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.481: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.481: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.481: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.482: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.482: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.484: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.484: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.484: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.484: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.485: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.485: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.485: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.485: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.485: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.485: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.486: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.486: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.486: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.486: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.487: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.487: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.487: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.487: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.487: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.487: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.488: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.488: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.489: Invalid UTF-8 string passed to pango_layout_set_text()
(process:6086): Pango-WARNING **: 17:57:47.489: Invalid UTF-8 string passed to pango_layout_set_text()
En los boxplots paralelos de precios por barrios se puede ver que entre los barrios que presentan mayores precios se ubican: Puerto Madero,Las Cañitas, Catalinas, Recoleta y Belgrano, mientras que Villa Soldati, Constitución, Villa lugano y la Boca presentan los menores precios de propiedades.
Probemos ajustar un modelo lineal para el precio en función de la superficie total y los barrios.
# ajustamos el modelo
modelo_sc_l3 <- lm(price ~ surface_covered + l3, data = train_data)
tidy_sc_l3 <- tidy(modelo_sc_l3, conf.int = TRUE)
tidy_sc_l3
R cuando efectúa la regresión calcula automáticamente las variables indicadoras (dummies) para las covariables categóricas, en general según orden alfabético. Podemos chequear el orden para verificar cuál es la categoría basal. En este caso, la categoría de referencia corresponde al barrio de Abasto.
¿Qué significan los coeficientes de esta variable categórica?
Este modelo propone ajustar una recta distinta para el precio medio de cada población definida por el barrio, todas con igual pendiente (definida por la superficie), y 57 ordenadas al origen diferentes, una por cada barrio.
Por ejemplo, eligiendo un barrio de precios altos, vemos que \(\beta_{Belgrano}\) indica cuánto se incrementa el precio medio de las propiedades para aquellos inmuebles ubicados en Belgrano respecto de aquellos ubicados en Abasto (categoría basal), dada la superficie.
Analizando un barrio de precios bajos, \(\beta_{Boca}\) indica cuánto se reduce el precio medio de las propiedades ubicadas en la Boca respecto de aquellas ubicadas en Abasto (categoría basal), dada la superficie.
Para evaluar la significativdad individual de cada una de las variables se analiza el test t que busca probar si el coeficiente de regresión correspondiente a dicha variable es distinto de 0 (figura en la tabla resumen de resultados de la regresión).
Es decir, busca probar:
\(H_0: \beta_k = 0\)
\(H_1: \beta_k ≠ 0\).
Modelo Superficie y Habitaciones
options("scipen"=1)
tidy_sc_r %>%
select(term, statistic, p.value, conf.low, conf.high)
En este primer modelo se observa que tanto la variable surface_total como rooms resultan estadísticamente significativas para explicar el precio de las propiedades (p-valores < 0.05).
Además del resultado del test, podemos apreciar que los intervalos de confianza (IC) del 95% de las variables de superficie y habitaciones no contienen al 0.
Modelo Superficie y Tipo de Propiedad
tidy_sc_pt %>%
select(term, statistic, p.value, conf.low, conf.high)
En este caso también se observa que todas las variables resultan estadísticamente significativas para explicar al precio de las propiedades (p-valores < 0.05).
Además del resultado del test, podemos corroborar que los intervalos de confianza del 95% para los coeficientes estimados no contienen al 0 en ninguno de los casos.
Modelo Superficie Total y Barrios
tidy_sc_l3 %>%
select(term, statistic, p.value, conf.low, conf.high) %>%
arrange(p.value)
En este modelo se observa que mientras la variable superficie resulta estadísticamente significativa para explicar al precio (p-valores < 0.05), las categorías de barrios no. Hay algunos que resultan significativos y otros no.
Esto mismo se observa a través de los intervalos de confianza del 95% donde algunos contienen al 0 (por ej. Caballito) y otros no (por ej. Puerto Madero).
Este test permite chequear si los valores medios del precio de propiedades son los mismos en los distintos barrios respecto de la categoría basal. Cabe destacar, que estos p-valores son válidos para las comparaciones individuales respecto de la categoría basal pero no abarcan todas las comparaciones de a pares.
Es decir, que los niveles medios de precio de inmuebles en los distintos barrios en algunos casos difieren de los niveles medios del Abasto (basal) y en otros no.
Si queremos evaluar las variables property_type o l3 en su conjunto, debemos recurrir a un test F.
El test conjunto F (y su correspondiente p-valor) permite medir la significatividad conjunta de una variable categórica para explicar la respuesta.
Test F se construye para testear si varios parámetros son cero, es decir, para probar las hipótesis:
\(H_0: β_1 = β_2 = · · · = β_{p−1} = 0\)
\(H_1:\) no todos los \(β_k\) (\(k = 1, 2,..., p−1\)) son iguales a 0.
Dichos tests F se obtienen para cada variable de la tabla de ANOVA del modelo. Veamos qué ocurre en este caso.
# Modelo Superficie Total y Tipo de Propiedad
tidy(anova(modelo_sc_pt))
La tabla de ANOVA muestra que, según el resultado del test F, la variable property_type en su conjunto resulta estadísticamente significativa para explicar al precio (p-valor < 0.05).
# Modelo Superficie Total y Barrios
tidy(anova(modelo_sc_l3))
La tabla de ANOVA muestra que, según el resultado del test F, la variable l3 en su conjunto resulta estadísticamente significativa para explicar al precio (p-valor < 0.05). Es decir, que pese a que algunas categorías en su comparación individual con la categoría basal sean poco significativas, la variable en su conjunto sí resulta significativa para el modelo.
Si este test no resultara significativo, suele descartarse la variable categórica de entre las covariables de interés, y se la excluye del modelo. Por el contrario, si este test resulta estadísticamente significativo, entonces suelen mirarse con más detalle cuáles de las comparaciones entre grupos son estadísticamente significativas, para proporcionar un mejor análisis de los datos.
Otra alternativa sería generar una nueva variable con menor número de variables categóricas que sean significativas, sin perder capacidad explicativa. Por ejemplo, se podría armar una nueva variable que agrupe los barrios.
tipo_barrioSe crea una nueva variable tipo_barrio que clasifica a los barrios según el precio por metro cuadrado promedio de las propiedades en ellos, de acuerdo a precios altos, medios y bajos.
Para ello, primero se genera una nueva variable de precios por metro cuadrado pxm2 para poder generar una clasificación del barrio en base a una decisión de negocio (lo que vale en promedio el metro cuadrado en cada barrio).
# Creamos una nueva variable de precios por metro cuadrado
train_data = train_data %>%
mutate(pxm2 = round(price/surface_total,0))
# Armamos un dataframe que muestre los promedios de pxm2 en cada barrio
AVG_pxm2_l3 = train_data %>%
group_by(l3) %>%
summarise(AVG_pxm2_l3 = mean(pxm2))
AVG_pxm2_l3
Observemos la distribución de los precios promedio por m2.
# boxplot de precios por metro cuadrado
ggplot(data = AVG_pxm2_l3, aes(x = AVG_pxm2_l3)) +
geom_boxplot(alpha = 0.75) +
labs(title = "Boxplot de precios promedio de barrios por m2") +
labs(x = "Precios promedio de barrios por m2")
Aplicaremos el siguiente criterio para agrupar los barrios en:
# armamos nueva variable siguiendo tales criterios
AVG_pxm2_l3 = AVG_pxm2_l3 %>%
mutate(tipo_barrio = case_when(
AVG_pxm2_l3 < quantile(AVG_pxm2_l3)[2] ~ "precio_bajo",
AVG_pxm2_l3 >= quantile(AVG_pxm2_l3)[2] & AVG_pxm2_l3 < quantile(AVG_pxm2_l3)[4] ~ "precio_medio",
TRUE ~ "precio_alto"
)
)
write.csv(AVG_pxm2_l3, 'AVG_pxm2_l3.csv')
# write_csv(AVG_pxm2_l3, 'AVG_pxm2_l3_.csv')
# unimos esta clasificación al dataset original
train_data = train_data %>% left_join(AVG_pxm2_l3, by = 'l3')
head(train_data)
Ajustamos el modelo con la nueva variable tipo_barrio en vez de l3.
# ajustamos el modelo
modelo_sc_tb <- lm(price ~ surface_covered + tipo_barrio, data = train_data)
tidy_sc_tb <- tidy(modelo_sc_tb, conf.int = TRUE)
tidy_sc_tb
¿Qué pasa ahora con la significatividad de los predictores?
Cuando las variables predictoras están correlacionadas entre sí, decimos que existe intercorrelación o multicolinealidad.
¿Qué pasa en nuestro dataset con la superficie cubierta y descubierta?
cor(train_data$surface_total, train_data$surface_covered)
[1] 0.9459646
Como ya habíamos visto en el gráfico ggpairs inicial, estas variables tienen alta correlación. Veamos qué ocurre con los coeficientes de ambas variables al armar distintos modelos múltiples que las incluyan.
Armamos un modelo con superficie total, superficie cubierta, habitaciones y tipo de propiedad.
modelo_stsc_r_pt <- lm(price ~ surface_total + surface_covered + rooms + property_type, data = train_data)
tidy(modelo_stsc_r_pt)
Armamos un modelo con superficie total y cubierta.
modelo_stsc <- lm(price ~ surface_total + surface_covered, data = train_data)
tidy(modelo_stsc)
Armamos un modelo con superficie total, habitaciones y tipo de propiedad pero sin contemplar superficie cubierta.
modelo_st_r_pt <- lm(price ~ surface_total + rooms + property_type, data = train_data)
tidy(modelo_st_r_pt)
Los coeficientes de regresión estimados se modifican sustancialmente cuando agregamos o quitamos variables del modelo. En el modelo que tiene las 4 variables vs el que solo tiene las superficies cubierta y total el beta estimado de la superficie total cambia de 918 a 412 y la cubierta de 2421 a 2516.
Los errores estándares de los estimadores de los coeficientes aumentan espúreamente cuando se incluyen covariables muy correlacionadas. Se infla la varianza estimada de los estimadores. En nuestro caso: el error estándar de la variable superficie total en el primer modelo donde está incluida la superficie cubierta es de alrededor de 918, mientras que en el modelo último que se excluye dicha variable es de 2560.
Veamos qué ocurre si en vez de usar la superficie total en el modelo último usamos la superficie cubierta.
modelo_sc_r_pt <- lm(price ~ surface_covered + rooms + property_type, data = train_data)
tidy(modelo_sc_r_pt)
Observamos que otra vez cambia el valor del estimador.
Los coeficientes pueden ser no significativos aún cuando exista una asociación verdadera entre la variable de respuesta y el conjunto de regresoras cuando armamos un modelo con multicolinealidad de variables regresoras.
Hay que tener cuidado con la colinealidad de los predictores para no tener problemas con la interpretación de los coeficientes del modelo lineal y que no aumenten espúreamente la varianza estimada de los estimadores.
Modelo con superficies total y cubierta
La interpretación de los coeficientes estimados sería:
\(\hat{\beta_{surface\_total}}\) indica que por cada m2 adicional de superficie total el precio esperado aumenta en 412 dólares, dada la superficie cubierta.
\(\hat{\beta_{surface\_covered}}\) indica que por cada m2 adicional de superficie cubierta el precio esperado aumenta en 2516 dólares, dada la superficie total.
Respecto a la segunda interpretación alguien podría objetar lo siguiente:
\(surface\_total = suface\_covered + suface\_uncovered\)
Entonces, si aumento en un m2 la superficie cubierta no puedo sostener que variable superficie total se mantiene constante.
¿Es esta observación correcta?
El coeficiente de \(\hat{\beta_{surface\_total}}\) en realidad nos permite evaluar cual es el efecto en el precio esperado de un m2 más de superficie descubierta para igual cantidad de m2 de superficie cubierta. Es decir, lo que cambia es la interpretación en este caso de dicho coeficiente.
Por ejemplo, si existen dos propiedades de 50m2 de superficie total y la propiedad A tiene 5 m2 de superficie descubierta y la propiedad B 4 m2, nuestro modelo nos indica que el precio predicho para la propiedad B va a ser 412 dólares menor al precio de la propiedad A.
Modelo superficies cubierta y descubierta
Si quisieramos poder separar el efecto de la superficie cubierta y descubierta deberíamos crear una variable nueva que sea:
\(surface\_uncovered = suface\_total + suface\_covered\)
Ahora nuestro modelo es:
\(E(precio|...) = \beta_0 + \beta_{sc}surface\_covered + \beta_{su}surface\_uncovered\)
Para ello creamos una nueva variable de superficie descubierta que sea resultado de la resta entre superficie total y cubierta.
df2 = train_data %>%
mutate(surface_uncovered = surface_total - surface_covered)
Construimos el modelo lineal que planteamos
tidy(lm(price ~ surface_covered + surface_uncovered, data = df2))
La interpretación de los coeficientes estimados es:
\(\hat{\beta_{suface\_covered}}\) indica que por cada m2 adicional de superficie cubierta el precio esperado de las propiedades aumenta en 2.929 dólares, dada la superficie descubierta.
\(\hat{\beta_{suface\_uncovered}}\) indica que por cada m2 adicional de superficie descubierta el precio esperado aumenta 412 dólares, dada la superficie cubierta.
En este caso la regresión ayuda a entender cómo afecta la superficie cubierta y descubierta al precio del inmueble. Es decir, cuánto aumenta el precio medio un m2 adicional de superficie cubierta, dada la superficie descubierta, y cuánto aumenta el precio promedio un m2 adicional de superficie descubierta, dada la superficie cubierta.
Volvemos a observar que el p valor asociado a \(\beta_2\) indica que no se puede rechazar la hipotesis nula del test de significatividad individual.